/*
 * 础石OS	-- 设备树
 *
 * Copyright (C) 2022 国科础石(重庆)软件有限公司
 *
 * 作者: Jingchen Zhong <zhongjingchen@kernelsoft.com>
 *	 Hengxing Wu <wuhengxing@kernelsoft.com>
 *
 * License terms: GNU General Public License (GPL) version 3
 *
 */

#include <dim-sum/errno.h>
#include <dim-sum/string.h>
#include <dim-sum/libfdt.h>
#include <dim-sum/init.h>
#include <dim-sum/mem.h>
#include <dim-sum/kernel.h>
#include <dim-sum/devtree.h>
#include <dim-sum/memory_regions.h>
#include <asm/sections.h>
#include <asm/devtree.h>



virt_addr_t devtree_virt;		//设备树blob起始虚地址
phys_addr_t devtree_phys_base;	//设备树blob物理地址
virt_addr_t devtree_virt_end;	// 设备树结束虚拟地址
virt_addr_t devtree_virt_base;
unsigned long devtree_virt_size;
u32 root_addr_cells, root_size_cells;

static u32 bank_nr;
static phys_addr_t bank_data[CONFIG_NR_MEM_BANK*2];

struct match_info {
	struct fdt_fileinfo *fdt;
	u32 visited_count;
	struct fdt_node_header *visited[CONFIG_NR_MEM_BANK];
};

/**
 * 死机函数
 */
static void start_die(void)
{
	while (1)
		;

}

static int __init memory_node_match(struct fdt_node_header *fdt_node, int level, void *priv)
{
	struct match_info *info = priv;
	const char *prop;
	int i;

	if (level == 1) {
		prop = libfdt_get_property(info->fdt, fdt_node, "device_type", NULL);
		if (NULL == prop)
			return 0;

		if (!strcmp(prop, "memory")) {
			for (i = 0; i < info->visited_count; i++) {
				if (info->visited[i] == fdt_node)
					return 0;
			}
			return 1;
		}
	}

	return 0;
}

static void __init bank_sort(void)
{
	phys_addr_t tmp = 0;
	u32 i = 0, j = 0;

	if(0 == bank_nr)
		return;

	for (i = 0; i < (bank_nr - 1); i++) {
		for (j = i + 1; j < bank_nr; j++) {
			if (bank_data[(2 * i)] > bank_data[(2 * j)]) {
				tmp = bank_data[(2 * i)];
				bank_data[(2 * i)] = bank_data[(2 * j)];
				bank_data[(2 * j)] = tmp;
				tmp = bank_data[(2 * i) + 1];
				bank_data[(2 * i) + 1] = bank_data[(2 * j) + 1];
				bank_data[(2 * j) + 1] = tmp;
			}
		}
	}
}

static u64 __init dt_mem_next_cell(int s, const __be32 **cellp)
{
	const __be32 *p = *cellp;

	*cellp = p + s;
	return dt_read_number(p, s);
}

/**
 * 从dtb中获取memory信息
 * 返回memory bank数量
 */
static int __init eayly_init_dt_scan_mem(void)
{
	struct fdt_node_header *fdt_node;
	struct fdt_fileinfo fdt;
	struct match_info info;
	const __be32 *prop;
	u32 index = 0;

	if (libfdt_parse_fileinfo((void *)devtree_virt, &fdt))
		return 0;

	memset(&info, 0, sizeof(info));
	info.fdt = &fdt;
	memset(bank_data, 0, sizeof(bank_data));

	bank_nr = 0;
	while ((info.visited_count < CONFIG_NR_MEM_BANK) && (index < ARRAY_SIZE(bank_data))) {
		u32 address_cells = root_addr_cells;
		u32 size_cells = root_size_cells;
		const __be32 *reg, *endp;
		int reg_size = 0;

		fdt_node = libfdt_find_matching_node(&fdt, memory_node_match, &info);
		if (!fdt_node)
			break;

		prop = libfdt_get_property(&fdt, fdt_node, "#address-cells", NULL);
		if (prop)
			address_cells = be32_to_cpup(prop);

		prop = libfdt_get_property(&fdt, fdt_node, "#size-cells", NULL);
		if (prop)
			size_cells = be32_to_cpup(prop);

		reg = libfdt_get_property(&fdt, fdt_node, "reg", &reg_size);
		if (NULL == reg)
			continue;

		endp = reg + (reg_size / sizeof(__be32));

		while ((endp - reg) >= (address_cells + size_cells)) {
			u64 base, size;

			base = dt_mem_next_cell(address_cells, &reg);
			size = dt_mem_next_cell(size_cells, &reg);
			if (size == 0)
				continue;

			bank_data[index++] = base;
			bank_data[index++] = size;
			bank_nr++;
		}

		info.visited[info.visited_count++] = fdt_node;
	}

	return bank_nr;
}

static int __init early_init_dt_scan_root(void)
{
	struct fdt_node_header *node;
	struct fdt_fileinfo fdt;
	const __be32 *prop;

	root_addr_cells = DT_ROOT_NODE_ADDR_CELLS_DEFAULT;
	root_size_cells = DT_ROOT_NODE_SIZE_CELLS_DEFAULT;

	if (libfdt_parse_fileinfo((void *)devtree_virt, &fdt))
		return -1;

	node = libfdt_find_node(&fdt, "/");
	if (!node)
		return -EINVAL;

	prop = libfdt_get_property(&fdt, node, "#address-cells", NULL);
	if (prop)
		root_addr_cells = be32_to_cpup(prop);

	prop = libfdt_get_property(&fdt, node, "#size-cells", NULL);
	if (prop)
		root_size_cells = be32_to_cpup(prop);

	return 0;
}

int __init nati_memory_bank_setup(void)
{
	int i, ret = 0;

	/*
	 * 解析得到memory信息
	 **/
	if (0 == eayly_init_dt_scan_mem()) {
		start_die();
	}

	/*
	 * 根据起始地址排序bank
	 **/
	bank_sort();

	for (i = 0; i < bank_nr*2; i += 2) {
		/**
		 * 把内存Bank加入内存区间方便管理
		 */
		ret = add_memory_regions(bank_data[i], bank_data[i+1]);
		if (ret)
			return ret;
	}

#ifdef CONFIG_ARCH_RK3588
	all_memory_regions.cnt = 2;
#endif
	return ret;
}

u32 __init get_mem_nr_bank(void)
{
	return bank_nr;
}

int __init get_mem_bank_start(u32 bank, phys_addr_t *addr)
{
	if (bank >= bank_nr)
		return -EINVAL;

	*addr = bank_data[bank*2];

	return 0;
}

int __init get_mem_bank_size(u32 bank, u64 *size)
{
	if (bank >= bank_nr)
		return -EINVAL;

	*size = bank_data[bank*2+1];

	return 0;
}

int __init get_mem_reserve_count(u32 *count)
{
	struct fdt_fileinfo fdt;
	int ret = 0;

	ret = libfdt_parse_fileinfo((void *)devtree_virt, &fdt);
	if (ret)
		return ret;

	*count = libfdt_reserve_count(&fdt);

	return 0;
}

int __init get_mem_reserve_addr(u32 index, phys_addr_t *addr)
{
	struct fdt_fileinfo fdt;
	u64 tmpaddr;
	u32 count;
	int ret = 0;

	ret = libfdt_parse_fileinfo((void *)devtree_virt, &fdt);
	if (ret)
		return ret;
	count = libfdt_reserve_count(&fdt);

	if (index < count) {
		ret = libfdt_reserve_address(&fdt, index, &tmpaddr);
		if (ret)
			return ret;

		*addr = (phys_addr_t)tmpaddr;
	} else {
		return -EINVAL;
	}

	return 0;
}

static int __init get_fdt_data(struct fdt_fileinfo *pfdt)
{
	int ret = 0;

	ret = libfdt_parse_fileinfo((void *)devtree_virt, pfdt);
	if (ret)
		return ret;
	return 0;
}

int __init get_mem_reserve_size(u32 index, u64 *size)
{
	struct fdt_fileinfo fdt;
	int ret = 0;
	u64 tmpaddr;
	u64 tmpsize;
	u32 count;

	ret = get_fdt_data(&fdt);
	if (ret)
		return ret;

	count = libfdt_reserve_count(&fdt);

	if (index < count) {
		ret = libfdt_reserve_address(&fdt, index, &tmpaddr);
		if (ret)
			return ret;

		ret = libfdt_reserve_size(&fdt, index, &tmpsize);
		if (ret)
			return ret;

		*size = tmpsize;
	} else {
		return -EINVAL;
	}

	return 0;
}

int __init nati_devtree_populate(struct device_node **dt_root)
{
	struct fdt_fileinfo fdt;
	int ret = 0;
	if (libfdt_parse_fileinfo((void *)devtree_virt, &fdt))
		return -1;
	ret = libfdt_populate_devtree(&fdt, dt_root, "\0", NULL);
	return ret;
}

/**
 * 早期设备树解析接口，获取memory、chosen、cmdline等信息
 * 此时设备树blob还未全部解析
 */
int __init early_init_dt_scan(void)
{
	int ret;

	/*
	 * 解析 root 节点 #address-cells 和 #size-cells
	 */
	ret = early_init_dt_scan_root();
	if (ret != 0)
		return ret;

	/**
	 * memory bank解析
	 */
	ret = nati_memory_bank_setup();
	return ret;
}

int __init early_init_dt_match_stdout(void)
{
	struct fdt_node_header *node, *node_parent;
	struct fdt_fileinfo fdt;
	const char *compatible;
	char stdout_path[64];
	char stdout_parent_path[64];
	const __be32 *prop;
	char *p, *opt, *opt_p;
	u32 address_cells;
	u64 reg;
	u32 len = 0;

	if (libfdt_parse_fileinfo((void *)devtree_virt, &fdt))
		return -EINVAL;
	node = libfdt_find_node(&fdt, "/chosen");
	if (!node)
		return -ENODEV;

	/*
	 * 解析得到stdout-path值
	 */
	p = libfdt_get_property(&fdt, node, "stdout-path", &len);
	if (!p) {
		p = libfdt_get_property(&fdt, node, "linux,stdout-path", &len);
	}

	if (!p || !len)
		return -ENODEV;

	/**
	 * 解析得到stdout-path设备和参数
	 */
	strlcpy(stdout_path, p, sizeof(stdout_path));
	strlcpy(stdout_parent_path, p, sizeof(stdout_parent_path));
	opt = strchr(stdout_path, ':');
	if (opt) {
		*opt = '\0';
		opt++;
	}

	opt_p = strrchr(stdout_parent_path, '/');
	if (opt_p) {
		*opt_p = '\0';
	}

	/**
	 * 得到console设备树节点
	 */
	node = libfdt_find_node(&fdt, stdout_path);
	if (!node)
		return -ENODEV;
	node_parent = libfdt_find_node(&fdt, stdout_parent_path);
	if (!node_parent)
		return -ENODEV;

	/**
	 * 得到console设备树节点reg值和compatible
	 */
	address_cells = root_addr_cells;
	prop = libfdt_get_property(&fdt, node_parent, "#address-cells", &len);
	if (prop) {
		address_cells = be32_to_cpup(prop);
	}
	prop = libfdt_get_property(&fdt, node, "reg", &len);
	if (!prop)
		return -ENODEV;
	reg = dt_read_number(prop, address_cells);

	compatible = libfdt_get_property(&fdt, node, "compatible", &len);
	if (!compatible)
		return -ENODEV;
	return setup_simple_console_by_name(compatible, len, reg, opt);
}
